/**
 * @file tty.c
 * @author lcz (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2022-11-06
 * 
 * @copyright Copyright (c) 2022
 * 
 */

#include "dev/tty.h"
#include "dev/dev.h"
#include "cpu/irq.h"
#include "tools/log.h "
#include "dev/kbd.h"
#include "dev/console.h"
#include "cpu/irq.h"

// 8个tty，组织成数组
static tty_t tty_devs[TTY_NR];
// 当前选中的tty
static int curr_tty = 0;
// tty open read close ... 的具体实现
// 驱动程序，后面各种设备的操作就是调用驱动程序
// 各种设备接口的抽象实现  data 这么用就是底层驱动的事了
// os提供的统一抽象接口
// 用主设备号及次设备（唯一标识一个设备）号返回设备id（唯一标识一个设备）

/**
 * @brief tty输出队列初始化
 */
static void tty_fifo_init(tty_fifo_t *fifo, char *buf, int size)
{
    fifo->buf = buf;
    fifo->count = 0;
    fifo->size = size;
    fifo->read = fifo->write = 0;
}
/**
 * @brief 设备的次设备号就是tty的编号
 */
static tty_t *get_tty(device_t *dev)
{
    int tty = dev->minor;
    if ((tty < 0) || (tty >= TTY_NR) || (!dev->open_count))
    {
        log_printf("tty is not opened,tty = %d", tty);
        return (tty_t *)0;
    }
    return &(tty_devs[tty]);
}

/**
 * @brief 将字符c放到指定的fifo队列中
 */
int tty_fifo_put(tty_fifo_t *fifo, char c)
{
    // 判断大小
    irq_state_t state = irq_enter_protection();
    if (fifo->count >= fifo->size)
    {
        irq_leave_protection(state);
        return -1;
    }
    fifo->buf[fifo->write++] = c;
    // 操作则调整
    if (fifo->write >= fifo->size)
    {
        fifo->write = 0;
    }
    fifo->count++;
    irq_leave_protection(state);
    return 0;
}
/**
 * @brief 从指定的队列中获取字符并放到c中
 */
int tty_fifo_get(tty_fifo_t *fifo, char *c)
{
    // 判断大小
    irq_state_t state = irq_enter_protection();
    if (fifo->count <= 0)
    {
        irq_leave_protection(state);
        return -1;
    }

    *c = fifo->buf[fifo->read++];
    // 操作则调整
    if (fifo->read >= fifo->size)
    {
        fifo->read = 0;
    }
    fifo->count--;
    irq_leave_protection(state);
    return 0;
}

/**
 * @brief tty 打开函数
 * @param dev 设备号
 * @return * int 
 */
int tty_open(device_t *dev)
{
    // 获取tty设备的次设备号，确定是使用那个终端
    int index = dev->minor;
    // 次设备号错误
    if ((index < 0) || (index >= TTY_NR))
    {
        log_printf("open tty failed, incrrect tty number = %d", index);
        return -1;
    }
    // 获取对应tty结构，对应相应终端
    tty_t *tty = tty_devs + index;
    // 初始化队列
    tty_fifo_init(&tty->ofifo, tty->obuf, TTY_OBUF_SIZE);
    sem_init(&tty->osem, TTY_OBUF_SIZE);
    tty_fifo_init(&tty->ififo, tty->ibuf, TTY_IBUF_SIZE);
    sem_init(&tty->isem, 0);
    tty->iflags = TTY_INCLR | TTY_IECHO;
    // 确定控制台下标
    tty->console_index = index;
    tty->oflags = TTY_OCRLF;

    kbd_init(); //不在有kernel_init调用,仅调用一次好吧
    console_init(index);
    return 0;
}
/**
 * @brief 将字符放入tty—put队列后调用consle_write真正实现打印
 * addr  buf 对于tty设备无须使用
 * @param dev
 * @param size
 * @return * int
 */
int tty_write(device_t *dev, int addr, char *buf, int size)
{
    // 参数合法型的校验
    if (size < 0)
    {
        return -1;
    }
    // 获取设备对应的tty
    tty_t *tty = get_tty(dev);
    if (!tty)
    {
        return -1;
    }
    // 跟具信号量调用函数将打印字符放到队列中
    int len = 0;
    while (size)
    {
        char c = *buf++;
        // 在输入一个换行之前添加一个回车 TTY_OCFLF
        if (c == '\n' && (tty->oflags & TTY_OCRLF))
        {
            sem_wait(&tty->osem);
            int err = tty_fifo_put(&tty->ofifo, '\r');
            if (err < 0)
            {
                break;
            }
        }
        // 处理字符
        sem_wait(&tty->osem);
        int err = tty_fifo_put(&tty->ofifo, c);
        if (err < 0)
        {
            break;
        }
        len++;
        size--;
        // 中断写入 判断显示器是否正在忙 忙继续 否就启动硬件发生显示
        // 我们没有显示器的中断，我们直接用console_write
        // 调用console_write后再调用console.c中的相关方法将打印的字符放到相应的现存中
        console_write(tty);
        //修改console_write
    }
    return size;
}

/**
 * @brief 从指定设备读取size大小的数据，当是buf不用tty从队列中读取，地址也不用
 * 用以接口
 * @param dev 设备
 * @param addr 读取的地址
 * @param buf 读取到的缓冲
 * @param size 大小
 */
int tty_read(device_t *dev, int addr, char *buf, int size)
{
    // size合法性校验
    if (size < 0)
    {
        return -1;
    }
    tty_t *tty = get_tty(dev);
    char *pbuf = buf;
    int len = 0;
    while (len < size)
    {
        // 没有就等
        sem_wait(&tty->isem);
        char ch;
        // 有就读，读取的字符到pbuf指针中
        tty_fifo_get(&tty->ififo, &ch);
        switch (ch)
        {
            // 特殊字符的处理
        case '\n':
            if ((tty->iflags & TTY_INCLR) && (len < size - 1))
            {
                *pbuf++ = '\r';
                len++;
            }
            *pbuf++ = '\n';
            len++;
            break;
        case 0x7f:
        // 处理删除（退格键），如果时退格键就就缓冲区减少
            if (len==0)
            {
                continue;
            }
            pbuf--;
            len--;
            break;
        default:
            (*pbuf)= ch;
            pbuf++;
            len++;
            break;
        }
        // 判断是否开启回显
        if (tty->iflags & TTY_IECHO)
        {
            // tty_fifo_put(&tty->ofifi,ch);
            tty_write(dev, 0, &ch, 1);
        }
        // 如果遇到回车和换行就停止
        if ((ch == '\n') || (ch == '\r'))
        {
            break;
        }
    }
    return len;
}

int tty_control(device_t *dev, int cmd, int arg0, int arg1)
{
    tty_t *tty = get_tty(dev);
    // 设置命令
    switch (cmd)
    {
        // 关闭回显命令 
    case TTY_CMD_ECHO:
        if (arg0)
        {
            tty->iflags |= TTY_IECHO;
        }
        else
        {
            tty->iflags &= ~TTY_IECHO;
        }
    }
}
void tty_close(device_t *dev)
{
    return;
}

/**
 * @brief tty编号idx，ch字符
 */
void tty_in(char ch)
{
    tty_t *tty = tty_devs + curr_tty;
    if (sem_count(&tty->isem) >= TTY_IBUF_SIZE)
    {
        return;
    }
    tty_fifo_put(&tty->ififo, ch);
    sem_notify(&tty->isem);
}
/**
 * @brief 切换tty
 * @param tty tty编号
 */
void tty_select(int tty)
{
    // 切换的目标不是当前目标
    if (tty != curr_tty)
    {
        console_select(tty);
        curr_tty = tty;
    }
}


// dev_tty_desc 统一接口  device_t
// 定义dev_tty_desc 设备大类
dev_desc_t dev_tty_desc = {
    .name = "tty",
    .major = DEV_TTY,
    .open = tty_open,
    .read = tty_read,
    .write = tty_write,
    .control = tty_control,
    .close = tty_close,
};